// Package errors provides structured error handling
package errors

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

// AppError represents an application error with context
type AppError struct {
	Code    string      `json:"code"`
	Message string      `json:"message"`
	Details interface{} `json:"details,omitempty"`
	Cause   error       `json:"-"`
}

// Error implements the error interface
func (e *AppError) Error() string {
	if e.Cause != nil {
		return fmt.Sprintf("%s: %s (caused by: %v)", e.Code, e.Message, e.Cause)
	}
	return fmt.Sprintf("%s: %s", e.Code, e.Message)
}

// Unwrap returns the underlying error
func (e *AppError) Unwrap() error {
	return e.Cause
}

// New creates a new application error
func New(code, message string) *AppError {
	return &AppError{
		Code:    code,
		Message: message,
	}
}

// Wrap wraps an existing error with context
func Wrap(err error, code, message string) *AppError {
	return &AppError{
		Code:    code,
		Message: message,
		Cause:   err,
	}
}

// WithDetails adds details to an error
func (e *AppError) WithDetails(details interface{}) *AppError {
	e.Details = details
	return e
}

// Common error codes
const (
	CodeInternal          = "INTERNAL_ERROR"
	CodeValidation        = "VALIDATION_ERROR"
	CodeNotFound          = "NOT_FOUND"
	CodeUnauthorized      = "UNAUTHORIZED"
	CodeForbidden         = "FORBIDDEN"
	CodeConflict          = "CONFLICT"
	CodeBadRequest        = "BAD_REQUEST"
	CodeServiceUnavailable = "SERVICE_UNAVAILABLE"
	CodeTimeout           = "TIMEOUT"
	CodeRateLimit         = "RATE_LIMIT_EXCEEDED"
)

// Common errors
var (
	ErrInternal          = New(CodeInternal, "An internal error occurred")
	ErrValidation        = New(CodeValidation, "Validation failed")
	ErrNotFound          = New(CodeNotFound, "Resource not found")
	ErrUnauthorized      = New(CodeUnauthorized, "Authentication required")
	ErrForbidden         = New(CodeForbidden, "Access denied")
	ErrConflict          = New(CodeConflict, "Resource conflict")
	ErrBadRequest        = New(CodeBadRequest, "Invalid request")
	ErrServiceUnavailable = New(CodeServiceUnavailable, "Service temporarily unavailable")
	ErrTimeout           = New(CodeTimeout, "Request timeout")
	ErrRateLimit         = New(CodeRateLimit, "Rate limit exceeded")
)

// HTTPStatusCode returns the appropriate HTTP status code for the error
func (e *AppError) HTTPStatusCode() int {
	switch e.Code {
	case CodeValidation, CodeBadRequest:
		return http.StatusBadRequest
	case CodeUnauthorized:
		return http.StatusUnauthorized
	case CodeForbidden:
		return http.StatusForbidden
	case CodeNotFound:
		return http.StatusNotFound
	case CodeConflict:
		return http.StatusConflict
	case CodeTimeout:
		return http.StatusRequestTimeout
	case CodeRateLimit:
		return http.StatusTooManyRequests
	case CodeServiceUnavailable:
		return http.StatusServiceUnavailable
	default:
		return http.StatusInternalServerError
	}
}

// Response represents an error response
type Response struct {
	Success   bool        `json:"success"`
	ErrorCode string      `json:"error_code"`
	Message   string      `json:"message"`
	Details   interface{} `json:"details,omitempty"`
	RequestID string      `json:"request_id,omitempty"`
}

// HandleError handles application errors and sends appropriate response
func HandleError(c *gin.Context, err error) {
	var appErr *AppError
	if errors.As(err, &appErr) {
		response := Response{
			Success:   false,
			ErrorCode: appErr.Code,
			Message:   appErr.Message,
			Details:   appErr.Details,
			RequestID: c.GetString("X-Request-ID"),
		}
		c.JSON(appErr.HTTPStatusCode(), response)
	} else {
		// Handle unknown errors
		response := Response{
			Success:   false,
			ErrorCode: CodeInternal,
			Message:   "An internal error occurred",
			RequestID: c.GetString("X-Request-ID"),
		}
		c.JSON(http.StatusInternalServerError, response)
	}
}

// SuccessResponse sends a success response
func SuccessResponse(c *gin.Context, data interface{}) {
	response := gin.H{
		"success":    true,
		"error_code": 0,
		"message":    "Success",
		"data":       data,
		"request_id": c.GetString("X-Request-ID"),
	}
	c.JSON(http.StatusOK, response)
}

// ErrorHandler middleware for centralized error handling
func ErrorHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Next()

		// Check if there are any errors to handle
		if len(c.Errors) > 0 {
			err := c.Errors.Last().Err
			HandleError(c, err)
		}
	}
}